/****************************************************************************
 *
 * Copyright (C) 2003-2008 Los Alamos National Security, LLC
 *                         Packet Analytics Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 as
 * published by the Free Software Foundation.  You may not use, modify or
 * distribute this program under any other version of the GNU General
 * Public License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 ****************************************************************************/
package nfse;

import java.io.File;
import java.util.Vector;
import java.net.ServerSocket;

import javax.net.ServerSocketFactory;

import nfse.listener.NetListenerServer;
import nfse.query.QueryServer;
import nfse.result.ResultServer;
import nfse.stats.StatsServer;
import nfse.alert.AlertServer;
import nfse.dataset.netflow.NetFlowThread;

/*******************************************************************************
 * Master application for running Net/FSE. <br>
 * <ul>
 * <li>Reads in configuration files</li>
 * <li>Starts various services:</li>
 * <ul>
 * <li>DataServer</li>
 * <li>QueryServer</li>
 * <li>ResultServer</li>
 * <li>AutoQuery</li>
 * <li>AutoMigrate</li>
 * <li>ResultServer</li>
 * </ul>
 * <li>Starts data collection mechanisms:</li>
 * <ul>
 * <li>NetFlow threads</li>
 * <li>Syslog threads</li>
 * </ul>
 * </ul>
 * 
 * @author bduphoff
 * 
 *         This code is the property of Los Alamos National Laboratory and the
 *         University of California, Copyright 2005
 */
public class NetFSEServer {

	private static Vector syslogThreads = new Vector();

	private static Vector netflowThreads = new Vector();

	private StatsServer statsServer = null;

	private AlertServer alertServer = null;

	/***************************************************************************
	 * Runs the NetFSE server according to the args parameter fields.
	 * 
	 * @param args
	 *            Usage: config_file node_config_file
	 */
	public static void main(String[] args) {
		try {
			NetFSEServer server = new NetFSEServer();

			NetFSEServer.NetFSEShutdown sh = server.new NetFSEShutdown();
			Runtime.getRuntime().addShutdownHook(sh);

			System.setProperty("java.protocol.handler.pkgs", "javax.net.ssl");

			if (args.length < 2) {
				System.out
						.println("Usage: java -jar server.jar config_file node_config_file");
				return;
			}

			NetFSE.CONFIG_FILE = args[args.length - 2];
			String configPath = NetFSE.CONFIG_FILE;
			File config = new File(configPath);
			if (config.exists())
				processConfigFile(config);
			else {
				throw (new Exception(
						"The specified configuration file does not exist: '"
								+ configPath + "'"));
			}
			String nodeConfigPath = args[args.length - 1];
			File config2 = new File(nodeConfigPath);
			if (config2.exists()) {
				NetFSE.processNodeConfigFile(config2);
			} else {
				throw (new Exception(
						"The specified node configuration file does not exist: '"
								+ nodeConfigPath + "'"));
			}

			server.init();

			/*******************************************************************
			 * Start the ControlSocket thread and wait for connection. Note: the
			 * ControlSocket is not fully implemented at this time but may be in
			 * a future release.
			 */
			ServerSocketFactory ssf = ServerSocketFactory.getDefault();
			ServerSocket ss = ssf
					.createServerSocket(NetFSE.CONTROL_SERVER_PORT);
			while (true) {
				ControlSocket cs = new ControlSocket(ss.accept());
				cs.start();
				System.out.println("Created new control socket.");
			}
		} catch (Exception E) {
			E.printStackTrace();
		}
	}

	/***************************************************************************
	 * Provides access to the stats server to other objects in the Net/FSE
	 * environment.
	 * 
	 * @return The Net/FSE stats server, only if this is the master node
	 *         (NetFSE.MASTER == true)
	 */
	public StatsServer getStatsServer() {
		return statsServer;
	}

	public void init() {
		try {
			/*******************************************************************
			 * Start the DataServer thread
			 */
			DataServer ds = new DataServer(NetFSE.DATA_SERVER_PORT, this);
			ds.start();
			System.out.println("Started data server.");

			
			
			/*******************************************************************
			 * Start the ResultServer thread
			 */
			ResultServer rs = new ResultServer(NetFSE.RESULT_SERVER_PORT);
			rs.start();
			System.out.println("Started result server.");

			/*******************************************************************
			 * Start the QueryServer thread
			 */
			QueryServer qs = new QueryServer(NetFSE.QUERY_SERVER_PORT, rs);
			qs.start();
			System.out.println("Started query server.");

			/*******************************************************************
			 * Start the NetListenerServer thread
			 */
			NetListenerServer ns = new NetListenerServer(
					NetFSE.NET_LISTENER_SERVER_PORT);
			ns.start();
			System.out.println("Started Net/Listener server.");

			
			Thread.sleep(5000);

			/***********************************************************
			 * Start the AutoMigrate thread with defaults of 30 days and min 10
			 * dynamic tables.
			 * 
			 * @TODO Change these values from hard-coded to configuration driven
			 *       parameters.
			 */
			AutoMigrate am = new AutoMigrate(30, 10, 20);
			am.start();
			//System.out.println("AutoMigrate is OFF");

			/*******************************************************************
			 * The operations that follow are only executed by the master
			 * Net/FSE server
			 */
			if (NetFSE.MASTER) {

				/***************************************************************
				 * Start the stats server to collect statistical information on
				 * the data streams being processed by Net/FSE.
				 */
				 statsServer = new StatsServer(NetFSE.STATS_SERVER_PORT);
				 statsServer.start();
				 System.out.println("Started stats server.");
				//System.out.println("StatsServer is OFF");

				/***************************************************************
				 * Start the alert server. This is newer code (not fully
				 * implemented) that can be commented out for now.
				 */
				// alertServer = new AlertServer(NetFSE.ALERT_SERVER_PORT);
				// alertServer.start();
				// System.out.println("Started alert server.");
				System.out.println("AlertServer is OFF.");

				/***************************************************************
				 * Open a connection to the metadata database.
				 */
				MetaDatabase mdb = MetaDatabase.create();

				/***************************************************************
				 * Start the Syslog threads
				 */
				SyslogThread[] syslogThreads = mdb.getSyslogThreads();
				for (int i = 0; i < syslogThreads.length; i++) {
					NetFSEServer.addSyslogThread(syslogThreads[i]);
					syslogThreads[i].start();
				}

				/***************************************************************
				 * Start the NetFlow threads
				 */
				NetFlowThread[] netflowThreads = mdb.getNetFlowThreads();
				for (int i = 0; i < netflowThreads.length; i++) {
					NetFSEServer.addNetFlowThread(netflowThreads[i]);
					netflowThreads[i].start();
				}

				// Done with the metadata connection; close it
				mdb.closeConnection();

				/***************************************************************
				 * This check should be commented out for a production build but
				 * is left in place for most testing.
				 */
				// if (false) {

				/***********************************************************
				 * Start the auto query (Critical Servers) thread
				 */
				AutoQuery aq = new AutoQuery();
				aq.start();

				/***********************************************************
				 * Start the thread to delete stale search results from the
				 * results database
				 */
				TempDBDelete del = new TempDBDelete();
				del.start();
				// }
			}
			
			Thread.sleep(2000);
			
			// Need more testing before deploying this by default
			//DataFileMonitorLauncher launcher = new DataFileMonitorLauncher();
			//launcher.start();
			//System.out.println("Started Data File Monitor Launcher.");
			System.out.println("Data File Monitor Launcher is OFF.");
			
		} catch (Exception E) {
			E.printStackTrace();
		}
	}

	/***************************************************************************
	 * Adds a newly defined syslog configuration to the system for future
	 * reference. Called by the init() method.
	 * 
	 * @param syslogThread
	 *            The SyslogThread object being added to the server
	 */
	public static synchronized void addSyslogThread(SyslogThread syslogThread) {
		for (int i = 0; i < syslogThreads.size(); i++) {
			SyslogThread temp = (SyslogThread) syslogThreads.elementAt(i);
			if (temp.getSensorID() == syslogThread.getSensorID())
				return; // Don't add a thread twice
		}
		syslogThreads.addElement(syslogThread);
	}

	/***************************************************************************
	 * Removes a syslog configuration to the system . This function is called by
	 * the ControlSocket to shutdown syslog threads dynamically.
	 * 
	 * @param syslogThread
	 *            The SyslogThread object being removed
	 */
	public static synchronized SyslogThread removeSyslogThread(int sensorID) {
		for (int i = 0; i < 0; i++) {
			SyslogThread syslogThread = (SyslogThread) syslogThreads
					.elementAt(i);
			if (syslogThread.getSensorID() == sensorID) {
				syslogThreads.removeElementAt(i);
				syslogThread.quit();
				return syslogThread;
			}
		}
		/***********************************************************************
		 * At this point the thread could not be found. An invalid sensorID or
		 * no such configuration.
		 */
		return null;
	}

	/***************************************************************************
	 * Adds a newly defined NetFlow configuration to the system for future
	 * reference. Called by the init() method.
	 * 
	 * @param syslogThread
	 *            The NetFlowThread object being added to the server
	 */
	public static synchronized void addNetFlowThread(NetFlowThread netflowThread) {
		for (int i = 0; i < netflowThreads.size(); i++) {
			NetFlowThread temp = (NetFlowThread) netflowThreads.elementAt(i);
			if (temp.getSensorID() == netflowThread.getSensorID())
				return; // Don't add a thread twice
		}
		netflowThreads.addElement(netflowThread);
	}

	/***************************************************************************
	 * Removes a NetFlow configuration from the system . This function is called
	 * by the ControlSocket to shutdown NetFlow threads dynamically.
	 * 
	 * @param netflowThread
	 *            The NetFlowThread object being removed
	 */
	public static synchronized NetFlowThread removeNetFlowThread(int sensorID) {
		for (int i = 0; i < 0; i++) {
			NetFlowThread netflowThread = (NetFlowThread) netflowThreads
					.elementAt(i);
			if (netflowThread.getSensorID() == sensorID) {
				netflowThreads.removeElementAt(i);
				netflowThread.quit();
				return netflowThread;
			}
		}
		/***********************************************************************
		 * At this point the thread could not be found. An invalid sensorID or
		 * no such configuration.
		 */
		return null;
	}

	/***************************************************************************
	 * Reads in the config file specified in the args
	 * 
	 * @param config
	 *            The file to process
	 * @exception Any
	 *                processing error will generate an exception
	 */
	private static void processConfigFile(File config) throws Exception {
		NetFSE.configure(config.getAbsolutePath());
	}

	/***************************************************************************
	 * This subclass is used to shutdown the Net/FSE server gracefully. The
	 * primary concern is shutting down the Syslog and NetFlow threads. Future
	 * versions should gracefully close any existing database connections and
	 * wait for any in-process operations (e.g. migration processes).
	 * 
	 * @author bduphoff
	 * 
	 */
	public class NetFSEShutdown extends Thread {
		public void run() {

			// Close all syslog threads
			Vector syslogThreads = NetFSEServer.syslogThreads;
			for (int i = 0; i < syslogThreads.size(); i++) {
				SyslogThread thread = (SyslogThread) syslogThreads.elementAt(i);
				Process p = thread.quit();
				thread.stop();
				p.destroy();
				System.out.flush();
				System.err.flush();
			}

			// Close all NetFlow threads
			Vector netflowThreads = NetFSEServer.netflowThreads;
			for (int i = 0; i < netflowThreads.size(); i++) {
				NetFlowThread thread = (NetFlowThread) netflowThreads
						.elementAt(i);
				thread.quit();
				System.out.flush();
				System.err.flush();
			}

		}
	}

}
